﻿namespace Refract.RIA
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.ServiceModel.DomainServices.Client;

    using Refract.Data;
    using Refract.MVVM;

    /// <summary>
    /// Service that reads and writes data using WCF RIA services
    /// </summary>
    /// <typeparam name="TContext">The type of the context.</typeparam>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    /// <typeparam name="TModel">The type of the model.</typeparam>
    /// <typeparam name="TListEntity">The type of the list entity.</typeparam>
    /// <typeparam name="TListModel">The type of the list model.</typeparam>
    public class ReadOnlyRiaDataService<TContext, TEntity, TModel, TListEntity, TListModel> : ReadOnlyDataService<TEntity, TListEntity, TListModel>
        where TContext : DomainContext
        where TEntity : Entity, new()
        where TModel : Model<TEntity>, new()
        where TListEntity : ICollection<TEntity>, new()
        where TListModel : Model<TListEntity>, new()
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="RiaDataService{TContext,TEntity,TModel,TListEntity,TListModel}"/> class.
        /// </summary>
        /// <param name="context">The context.</param>
        public ReadOnlyRiaDataService(TContext context)
        {
            Context = context;
        }

        #endregion Constructors

        #region Properties

        /// <summary>
        /// Gets or sets the context.
        /// </summary>
        /// <value>The context.</value>
        public TContext Context
        {
            get; set;
        }

        #endregion Properties

        #region Methods

        /// <summary>
        /// Gets a list of all records for this entity.
        /// </summary>
        /// <returns></returns>
        public override TListModel GetList()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Internal method to get a specific records.
        /// </summary>
        /// <param name="query">The query.</param>
        /// <param name="filter">The filter.</param>
        /// <returns></returns>
        protected TModel GetInternal(EntityQuery<TEntity> query, Expression<Func<TEntity, bool>> filter = null)
        {
            TModel model = new TModel();

            if (filter != null)
            {
                query = query.Where(filter);
            }

            try
            {
                ProcessCount++;
                Context.Load(query, OnLoadComplete, model);
            }
            catch (Exception exception)
            {
                throw new DomainOperationException("Error loading a " + typeof(TEntity).Name + ".",
                                                   exception);
            }
            return model;
        }

        /// <summary>
        /// Internal method to get a list of records.
        /// </summary>
        /// <param name="query">The query.</param>
        /// <returns></returns>
        protected TListModel GetListInternal(EntityQuery<TEntity> query)
        {
            var model = new TListModel();

            try
            {
                ProcessCount++;
                Context.Load(query, OnListLoadComplete, model);
            }
            catch (Exception exception)
            {
                throw new DomainOperationException("Error loading a list of " + typeof(TEntity).Name + " entities.",
                                                   exception);
            }
            return model;
        }

        /// <summary>
        /// Called when list load operatio is complete.
        /// </summary>
        /// <param name="operation">The operation.</param>
        protected virtual void OnListLoadComplete(LoadOperation<TEntity> operation)
        {
            OnOperationComplete(operation);

            var entityList = new TListEntity();
            foreach (TEntity entity in operation.Entities)
            {
                entityList.Add(entity);
            }

            var model = operation.UserState as TListModel;

            if (model != null)
            {
                model.Data = entityList;

            }
            else
            {
                var action = operation.UserState as Action<TListEntity>;
                if (action != null)
                {
                    action(entityList);
                }
            }
            RaiseEntityListLoadComplete(entityList);
        }

        /// <summary>
        /// Called when load operation is complete.
        /// </summary>
        /// <param name="operation">The operation.</param>
        protected virtual void OnLoadComplete(LoadOperation<TEntity> operation)
        {
            OnOperationComplete(operation);

            var entity = operation.Entities.Single();

            var model = operation.UserState as TModel;

            if (model != null)
            {
                model.Data = entity;

            }
            else
            {
                var action = operation.UserState as Action<TEntity>;
                if (action != null)
                {
                    action(entity);
                }
            }

            RaiseEntityLoadComplete(entity);
        }

        /// <summary>
        /// Called when an operation is complete.
        /// </summary>
        /// <param name="operation">The operation.</param>
        protected virtual void OnOperationComplete(OperationBase operation)
        {
            ProcessCount--;
            if (operation.Error != null)
            {
                HandleError(operation.Error);
                return;
            }
        }

        #endregion Methods
    }
}